package com.app.system.permission.web;

import com.app.log.service.LogSystemService;
import com.app.system.config.jwt.JwtToken;
import com.app.system.config.properties.SystemParamProperties;
import com.app.system.permission.model.DataDict;
import com.app.system.permission.model.UserInfo;
import com.app.system.permission.service.DataDictService;
import com.app.system.permission.service.UserInfoService;
import com.app.system.utils.ParamUtils;
import com.app.system.utils.WebUtils;
import com.app.system.utils.base64.Base64Utils;
import com.app.system.utils.base64.UkeyBase64;
import com.app.system.utils.dataType.StringUtils;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * 系统登录
 * Created by wcf-pc on 2018/5/15.
 *
 * @author wcf-pc
 */
@Controller
@RequestMapping("/login/*")
@SuppressWarnings("all")
public class LoginController {


    private static final Logger logger = LoggerFactory.getLogger(LoginController.class);

    @Resource
    private LogSystemService logSystemService;
    @Resource
    private UserInfoService userInfoService;
    @Resource
    private DataDictService dataDictService;
    @Resource
    private SystemParamProperties systemParamProperties;

    @RequestMapping({"main"})
    public void main(HttpServletRequest request, ModelMap modelMap) {
    }

    /**
     * 手机登录页面
     *
     * @param request
     * @param modelMap
     */
    @RequestMapping({"mLogin"})
    public void mLogin(HttpServletRequest request, ModelMap modelMap) {
        Object obj = SecurityUtils.getSubject().getPrincipal();
        if (obj != null && !"".equals(obj.toString())) {//当前已有登录用户
            WebUtils.redirect("/");//直接进入主界面
        }
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");
        modelMap.addAttribute("dataDictMap", dataDictMap);
    }

    @RequestMapping({"login"})
    public void login(HttpServletRequest request, ModelMap modelMap) {
        Object obj = SecurityUtils.getSubject().getPrincipal();
        if (obj != null && !"".equals(obj.toString())) {//当前已有登录用户
            WebUtils.redirect("/");//直接进入主界面
        }
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");
        modelMap.addAttribute("dataDictMap", dataDictMap);
        modelMap.addAttribute("keyUsed", systemParamProperties.isKeyUsed());
    }

    @RequestMapping({"zjLogin"})
    public void zjLogin(HttpServletRequest request, ModelMap modelMap) {
        Object obj = SecurityUtils.getSubject().getPrincipal();
        if (obj != null && !"".equals(obj.toString())) {//当前已有登录用户
            WebUtils.redirect("/");//直接进入主界面
        }
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");
        modelMap.addAttribute("dataDictMap", dataDictMap);
        modelMap.addAttribute("keyUsed", systemParamProperties.isKeyUsed());
    }

    /**
     * 后台登录界面
     *
     * @param model
     * @param request
     */
    @RequestMapping({"adminLogin"})
    public void adminLogin(ModelMap model, HttpServletRequest request) {
        Object obj = SecurityUtils.getSubject().getPrincipal();
        if (obj != null && !"".equals(obj.toString())) {//当前已有登录用户
            WebUtils.redirect("/");//直接进入主界面
        }
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");
        model.addAttribute("dataDictMap", dataDictMap);
    }

    @PostMapping("adminValidate")
    @ResponseBody
    public Map adminValidate(HttpServletRequest request, String username, String password, Boolean rememberMe) {
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");
        Map<String, Object> map = Maps.newHashMap();
        String message = "";
        int status = 0;
        username = Base64Utils.decode(username);
        password = Base64Utils.decode(password);
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();

        //获取当前已登录的该用户session，为踢出用户准备数据
        Session currentUserSession = this.getCurrentUserSession(username);

        JwtToken token = new JwtToken(username, password, rememberMe);
        if (status == 0) {

            try {
                //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
                //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
                //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
                logger.info("对用户[" + username + "]进行登录验证..验证开始（后台登录）");
                currentUser.login(token);
                logger.info("对用户[" + username + "]进行登录验证..验证通过（后台登录）");
            } catch (UnknownAccountException uae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
//                message = "未知账户";
                message = "用户名或密码不正确";
                status = 1;
            } catch (IncorrectCredentialsException ice) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
//                message = "密码不正确";
                message = "用户名或密码不正确";
                status = 2;
            } catch (LockedAccountException lae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
                message = "账户已锁定";
                status = 3;
            } catch (ExcessiveAttemptsException eae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
                message = "用户名或密码错误次数过多";
                status = 4;
            } catch (AuthenticationException ae) {
                //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
//            ae.printStackTrace();
                message = "用户名或密码不正确";
                status = 5;
            }
        }
//        }
        //验证是否登录成功
        if (currentUser.isAuthenticated()) {
            if (currentUserSession != null && !systemParamProperties.getMoreLogin()) {
                //同一账号不允许重复登录
                currentUserSession.setTimeout(0);//设置session立即失效，即将其踢出系统
            }
            logSystemService.addLog(request, username, "用户登录（后台登录）", "com.app.system.permission.web.LoginController.validate");
        } else {
            token.clear();
        }
        map.put("status", status);
        map.put("message", message);
        return map;
    }

    @PostMapping("validate")
    @ResponseBody
    public Map validate(HttpServletRequest request, String username, String password, String randomcode, Boolean rememberMe) {
        //// TODO: 2020/7/2 记住密码 
        rememberMe = true;
        Map<String, DataDict> dataDictMap = dataDictService.getMapByParentNameForName("系统配置");

        Map<String, Object> map = Maps.newHashMap();
        String message = "";
        int status = 0;
        username = Base64Utils.decode(username);
        password = Base64Utils.decode(password);
        Subject currentUser = SecurityUtils.getSubject();
        Session session = currentUser.getSession();
        randomcode = Base64Utils.decode(randomcode);
        //转化成小写字母
        randomcode = randomcode.toLowerCase();
        String v = (String) session.getAttribute("validateCode");
        if (!randomcode.equals(v)) {
            status = 1;
            message = "验证码不正确或过期，请重新输入";
        } else if (Strings.isNullOrEmpty(username) || Strings.isNullOrEmpty(password)) {
            status = 1;
            message = "用户名或密码为空";
        }
        session.removeAttribute("validateCode");


        if (status == 0) {
            if (systemParamProperties.isKeyUsed()) {
                String clientDigest = ParamUtils.getString(request, "clientDigest", "");
                String randData = (String) request.getSession().getAttribute("randData");
                UserInfo userInfo = this.userInfoService.findByUsername(username);
                String ukey = userInfo.getUkey();

                boolean value = this.checkHashValues(ukey, randData, clientDigest);

                if (!value) {
                    status = 2;
                    message = "钥匙盘错误，请联系技术支持";
                }
            }
        }

        UserInfo user = this.userInfoService.findByUsername(username);
        String hashAlgorithmName = systemParamProperties.getHashedCredentialsMatcherHashAlgorithmName();//加密方式
        String crdentials = password;//密码原值
        ByteSource credentialsSalt = user == null ? null : ByteSource.Util.bytes(user.getUsername() + user.getSalt());//以账号+salt作为盐值
        int hashIterations = systemParamProperties.getHashedCredentialsMatcherHashIterations();//加密1024次
        String newPassword = new SimpleHash(hashAlgorithmName, crdentials, credentialsSalt, hashIterations).toString();

        //获取当前已登录的该用户session，为踢出用户准备数据
        Session currentUserSession = this.getCurrentUserSession(username);

        JwtToken token = new JwtToken(username, newPassword, rememberMe);
        if (status == 0) {
            try {
                //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
                //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
                //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
                logger.info("对用户[" + username + "]进行登录验证..验证开始");
                currentUser.login(token);
                logger.info("对用户[" + username + "]进行登录验证..验证通过");
            } catch (UnknownAccountException uae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户");
//                message = "未知账户";
                message = "用户名或密码不正确";
                status = 1;
            } catch (IncorrectCredentialsException ice) {
                ice.printStackTrace();
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证");
//                message = "密码不正确";
                message = "用户名或密码不正确";
                status = 2;
            } catch (LockedAccountException lae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定");
                message = "账户已锁定";
                status = 3;
            } catch (ExcessiveAttemptsException eae) {
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多");
                message = "用户名或密码错误次数过多";
                status = 4;
            } catch (AuthenticationException ae) {
                //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
//                logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下");
//            ae.printStackTrace();
                message = "用户名或密码不正确";
                status = 5;
            }
        }
//        }
        //验证是否登录成功
        if (currentUser.isAuthenticated()) {
            if (currentUserSession != null && !systemParamProperties.getMoreLogin()) {
                //同一账号不允许重复登录
                currentUserSession.setTimeout(0);//设置session立即失效，即将其踢出系统
            }
            logSystemService.addLog(request, username, "用户登录", "com.app.system.permission.web.LoginController.validate");
        } else {
            token.clear();
        }
        map.put("status", status);
        map.put("message", message);
        return map;
    }

    private Session getCurrentUserSession(String username) {
        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        DefaultWebSessionManager sessionManager = (DefaultWebSessionManager) securityManager.getSessionManager();
        Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();//获取当前已登录的用户session列表
        if (!CollectionUtils.isEmpty(sessions)) {
            for (Session session : sessions) {
                if (username.equals(String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)))) {
                    return session;
                }
            }
        }
        return null;
    }

    @GetMapping("logout")
    public String logout(RedirectAttributes redirectAttributes, HttpServletRequest request) {
        //使用权限管理工具进行用户的退出，跳出登录，给出提示信息
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        if (StringUtils.isNotEmpty(username)) {
            SecurityUtils.getSubject().logout();
            logSystemService.addLog(request, username, "用户退出", "com.app.system.permission.web.LoginController.logout");
//            redirectAttributes.addFlashAttribute("message", "您已安全退出");
        }
        return "redirect:/";
    }

    @GetMapping("minLogin")
    public void minLogin(HttpServletRequest request, ModelMap modelMap) {
        this.login(request, modelMap);
    }

    @GetMapping("validateIsLogin")
    @ResponseBody
    public String validateIsLogin() {
        String result = "login";//默认登录中
        Object obj = SecurityUtils.getSubject().getPrincipal();
        if (obj == null || "".equals(obj)) {
            result = "logout";//已经退出
        }
        return result;
    }

    @RequestMapping
    @ResponseBody
    public Map loadUser(HttpServletRequest request, String guid) {
        UserInfo user = this.userInfoService.getUserByGuid(guid);
        String status = "0";
        String username = "null";
        if (user != null) {
            status = "1";
            username = user.getUsername();
        }

        String randData = "";
        int b = 0;
        int a = 0;
        Random r = new Random();
        for (int i = 0; i < 32; i++) {
            a = r.nextInt(26);
            b = (char) (a + 65);
            randData += new Character((char) b).toString();
        }
        request.getSession().setAttribute("randData", randData);

        Map<String, String> jsonMap = new HashMap();
        jsonMap.put("status", status);
        jsonMap.put("username", username);
        jsonMap.put("randomStr", new String(UkeyBase64.encode(randData.getBytes())));
        return jsonMap;
    }

    public boolean checkHashValues(String seed, String random, String clientDigest) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("SHA1");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        String a = random + seed;
        byte[] serverDigest = md.digest(a.getBytes());

        byte[] clientDigest1 = UkeyBase64.decode(clientDigest);
        return Arrays.equals(serverDigest, clientDigest1);
    }
}
